﻿/*************************************************************************
Diagrams library
Copyright (c) 2010 Pavel Torgashov
          (c) 2013 Timur Karimov
>>> LICENSE >>>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation (www.fsf.org); either version 2 of the
License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

A copy of the GNU General Public License is available at
http://www.fsf.org/licensing/licenses
>>> END OF LICENSE >>>
*************************************************************************/

using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing.Drawing2D;
using System.Drawing;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using System.Runtime.Serialization;
using System.Reflection;
using System.Linq;
using System.ComponentModel;

namespace Diagrams
{
    //определяют тип порта
    [Serializable]
    public enum ConnectorDirections { Input, Output };
    [Serializable]
    public enum ConnectorOrientations { LeftToRight = 0, RightToLeft = 1, TopToBottom = 2, BottomToTop = 3};
    //исключение, возникающее, когда линия соединяет порты некорректно
    [Serializable]
    public class ConnectorException : Exception
    {
            public ConnectorException(string message) : base(message) { }
    }
    //варианты направлений, в которых размер фигуры может быть изменен
    [Serializable]
    public enum ResizeDirections { None, Vertical, Horizontal, Both, BothByCornerMarkers };
    //диаграмма
    [Serializable]
    public class Diagram : ICloneable
    {
        //имя диаграммы
        public string Name;
        //список фигур диаграммы
        public readonly List<Figure> figures = new List<Figure>();
        //сохранение диаграммы в файл
        public void Save(string fileName)
        {
            using (FileStream fs = new FileStream(fileName, FileMode.Create))
                new BinaryFormatter().Serialize(fs, this);
        }
        public void Save(FileStream fs)
        {
            new BinaryFormatter().Serialize(fs, this);
        }
        //чтение диаграммы из файла
        public static Diagram Load(string fileName)
        {
            using (FileStream fs = new FileStream(fileName, FileMode.Open))
                return (Diagram)new BinaryFormatter().Deserialize(fs);
        }
        public static Diagram Load(FileStream fs)
        {
           return (Diagram)new BinaryFormatter().Deserialize(fs);
        }
        public object Clone()
        {
            //создаем новую диаграмму, куда копируем фигуры из текущей диаграммы
            Diagram d = new Diagram();
            d.Name = Name;
            int i, j, Q;
            ComplexFigure tmp;
            LineFigure line;
            //копирование фигур в диаграмму
            for (i = 0; i < this.figures.Count; i++)
                d.figures.Add(this.figures[i].Clone() as Figure);
            //установление связей между скопированными линиями и скопированными фигурами
            for (i = 0; i < d.figures.Count; i++)
            {
                if (d.figures[i] is LineFigure)
                {
                    //нашли линию
                    line = (d.figures[i] as LineFigure);
                    Q = 0;//число переподключенных концов
                    for (j = 0; j < d.figures.Count; j++)
                    {
                        //нашли некую сложную фигуру
                        if (d.figures[j] is ComplexFigure)
                        {
                            tmp = (d.figures[j] as ComplexFigure);
                            if (line.From == null)
                                Q++;
                            else if (line.From.OwnerFigure == null)
                                Q++;//один конец фигуры линии уже подключен к "ничему"
                            else if (line.From.OwnerFigure.Name == tmp.Name)
                            {
                                //определили, что линия к ней подключена точкой From
                                foreach (SolidFigure connector in tmp.SolidFigures)
                                {
                                    if ((line.From as Figure).Name == connector.Name)
                                    {
                                        line.From = connector as IConnectable;
                                        Q++;
                                        break;
                                    }
                                }
                            }
                            if (line.To == null)
                                Q++;
                            else if (line.To.OwnerFigure == null)
                                Q++;//один конец фигуры линии уже подключен к "ничему"
                            else if (line.To.OwnerFigure.Name == tmp.Name)
                            {
                                //определили, что линия к ней подключена точкой From
                                foreach (SolidFigure connector in tmp.SolidFigures)
                                {
                                    if ((line.To as Figure).Name == connector.Name)
                                    {
                                        line.To = connector as IConnectable;
                                        Q++;
                                        break;
                                    }
                                }
                            }
                            if (Q >= 2) //значит, линию уже полностью переподключили
                                break;
                        }
                    }
                }
            }
            return d as Object;
        }
    }

    //фигура
    [Serializable]
    public abstract class Figure : ICloneable
    {
        //идентификатор
        protected string name;
        static Random rnd = new Random(DateTime.Now.Second);
        /// <summary>
        /// Присваивает элементу случайное имя из 8 символов
        /// </summary>
        public void SetRandomName8()
        {
            name = "";
            for (int i = 0; i < 8; i++)
                name += (char)(rnd.Next(26) + 'A');
        }
        //линии фигуры
        public SerializableGraphicsPath serializablePath = new SerializableGraphicsPath();
        public GraphicsPath Path
        { 
            get { return serializablePath.path; }
            set { serializablePath.path = value; }
        }
        //карандаш отрисовки линий
        public Pen FigurePen {
            get { return serializablePath.pen; }
            set { serializablePath.pen = value; }
        }
        public string Name
        {
            get { return name; }
            set { name = value; }
        }
        //точка находится внутри фигуры?
        public abstract bool IsInsidePoint(Point p);

        //отрисовка фигуры
        public abstract void Draw(Graphics gr);

        //получение маркеров
        public abstract List<Marker> CreateMarkers(Diagram diagram);

        //смещение фигуры
        public abstract void Offset(float dx, float dy);

        //прямоугольник вокруг фигуры (в абсолютных координатах)
        public abstract RectangleF Bounds
        {
            get;
        }
        /// <summary>
        /// Создает копию текущей фигуры
        /// </summary>
        /// <returns></returns>
        public abstract object Clone();
    }
    //интерфейс, который определяет, содержит ли фигура сплошные элементы
    public interface ISolidFigure
    {
        SizeF Size
        {
            get;
            set;
        }
        PointF Location
        {
            get;
            set;
        }
        Brush FigureBrush
        {
            get;
            set;
        }
        Pen FigurePen
        {
            get;
            set;
        }
    }
    //многоугольник с текстом внутри
    [Serializable]
    public class SolidFigure: Figure, ISolidFigure
    {
        //размер новой фигуры, по умолчанию
        public int defaultSize = 40;
        //заливка фигуры
        public Brush FigureBrush
        {
            get { return serializablePath.brush; }
            set { serializablePath.brush = value; }
        }
        //местоположение центра фигуры
        public PointF location;
        //прямоугольник, в котором расположен текст
        public RectangleF textRect;
        //текст
        public string text = null;
        //шрифт, которым написан текст
        public Font textFont = SystemFonts.DefaultFont;
        //цвет шрифта, которым написан текст
        public Brush textColor = Brushes.Black;
        //ссылка на сложную фигуру, частью которой является эта
        public Figure ownerFigure = null;
        public Figure OwnerFigure
        {
            get { return ownerFigure; }
            set { ownerFigure = value; }
        }
        //направления, в которых может быть изменена фигура
        public ResizeDirections resizeDirections = ResizeDirections.BothByCornerMarkers;
        //разрешение изменять текст на фигуре
        protected bool textChangeEnable = true;
        public bool TextChangeEnable
        {
            get { return textChangeEnable; }
            set { textChangeEnable = value; }
        }
        //разрешение автоматически подгонять размер под текст
        protected bool autoSizeEnable = false;
        public bool AutoSizeEnable
        {
            get { return autoSizeEnable; }
            set { autoSizeEnable = value; }
        }
        //настройки вывода текста
        public virtual StringFormat StringFormat
        {
            get {
                StringFormat stringFormat = new StringFormat();
                stringFormat.Alignment = StringAlignment.Center;
                stringFormat.LineAlignment = StringAlignment.Center;
                return stringFormat;
            }
        }
        //находится ли точка внутри контура?
        public override bool IsInsidePoint(Point p)
        {
            return Path.IsVisible(p.X - location.X, p.Y - location.Y);
        }
        
        //прямоугольник вокруг фигуры (в абсолютных координатах)
        public override RectangleF Bounds
        {
            get
            {
                RectangleF bounds = Path.GetBounds();
                return new RectangleF(bounds.Left + location.X, bounds.Top + location.Y, bounds.Width, bounds.Height);
            }
        }

        //прямоугольник текста (в абсолютных координатах)
        public Rectangle TextBounds
        {
            get
            {
                return new Rectangle((int)textRect.Left + (int)location.X, (int)textRect.Top + (int)location.Y, (int)textRect.Width, (int)textRect.Height);
            }
        }

        //размер прямоугольника вокруг фигуры
        public SizeF Size
        {
            get { return Path.GetBounds().Size; }
            set
            {
                SizeF oldSize = Path.GetBounds().Size;
                SizeF newSize = new SizeF(Math.Max(1, value.Width), Math.Max(1, value.Height));
                //коэффициент шкалировани по x
                float kx = newSize.Width / oldSize.Width;
                //коэффициент шкалировани по y
                float ky = newSize.Height / oldSize.Height;
                Scale(kx, ky);
                //переопределение заливки
                if (FigureBrush is LinearGradientBrush)
                {
                    FigureBrush = new LinearGradientBrush(Path.GetBounds(),
                        (FigureBrush as LinearGradientBrush).LinearColors[0],
                        (FigureBrush as LinearGradientBrush).LinearColors[1], (float)0);
                }
            }
        }

        //изменение масштаба фигуры
        public void Scale(float scaleX, float scaleY)
        {
            //масштабируем линии
            Matrix m = new Matrix();
            m.Scale(scaleX, scaleY);
            Path.Transform(m);
            //масштабируем прямоугльник текста
            textRect = new RectangleF(textRect.Left * scaleX, textRect.Top * scaleY, textRect.Width * scaleX, textRect.Height * scaleY);
        }

        //сдвиг местоположения фигуры
        public override void Offset(float dx, float dy)
        {
            location.X += dx;
            location.Y += dy;
        }
        /// <summary>
        /// Автоматически подгоняет размер фигуры под текст
        /// </summary>
        public void AutoSize()
        {
            Size = System.Windows.Forms.TextRenderer.MeasureText(text + "   ", textFont);
        }
        //отрисовка фигуры
        public override void Draw(Graphics gr)
        {
            gr.TranslateTransform(location.X, location.Y);
            gr.FillPath(FigureBrush, Path);
            gr.DrawPath(FigurePen, Path);
            if (!string.IsNullOrEmpty(text))
                gr.DrawString(text, textFont, textColor, textRect, StringFormat);
            gr.ResetTransform();
        }

        //создание маркера для изменения размера
        public override List<Marker> CreateMarkers(Diagram diagram)
        {
            List<Marker> markers = new List<Marker>();
            Marker m0 = new BottomRightMarker();
            Marker m1 = new BottomLeftMarker();
            Marker m2 = new TopRightMarker();
            Marker m3 = new TopLeftMarker();
            Marker mrw = new RightWidthMarker();
            Marker mlw = new LeftWidthMarker();
            Marker mth = new TopHeightMarker();
            Marker mbh = new BottomHeightMarker();
            m0.targetFigure = m1.targetFigure = m2.targetFigure = m3.targetFigure =
            mrw.targetFigure = mlw.targetFigure = mth.targetFigure = mbh.targetFigure =
            this;
            //добавляем угловые маркеры, если можно изменить фигуру во всех направлениях
            if (resizeDirections == ResizeDirections.Both ||
                resizeDirections == ResizeDirections.BothByCornerMarkers)
            {
                markers.Add(m0);
                markers.Add(m1);
                markers.Add(m2);
                markers.Add(m3);
                if(resizeDirections == ResizeDirections.BothByCornerMarkers)
                    return markers;
            }
            //добавляем маркеры изменения по горизонтальной оси
            if (resizeDirections != ResizeDirections.Vertical &&
                resizeDirections != ResizeDirections.None)
            {
                markers.Add(mrw);
                markers.Add(mlw);
            }
            //добавляем маркеры изменения по вертикальной оси
            if (resizeDirections != ResizeDirections.Horizontal &&
                resizeDirections != ResizeDirections.None)
            {
                markers.Add(mbh);
                markers.Add(mth);
            }
            return markers;
        }
        //свойство "положение"
        public PointF Location
        {
            get { return location; }
            set { location = value; }
        }
        //свойство "текст"
        public string Text
        {
            get { return text; }
            set
            {
                text = value;
                if (autoSizeEnable)
                    AutoSize();
            }
        }
        public override object Clone()
        {
            object figure = Activator.CreateInstance(this.GetType());
            FieldInfo[] fields = figure.GetType().GetFields();
            //неглубокое копирование полей, допускающих это
            int i = 0;
            foreach (FieldInfo fi in this.GetType().GetFields())
            {
                if(fi.Name != "serializablePath"
                    && fi.Name != "textRect")
                    fields[i].SetValue(figure, fi.GetValue(this));
                i++;
            }
            //установка ряда свойств, требующих клонирование
            (figure as Figure).serializablePath = new SerializableGraphicsPath();
            (figure as Figure).FigurePen = (Pen)(this as Figure).FigurePen.Clone();
            (figure as Figure).Path = (GraphicsPath)(this as Figure).Path.Clone();
            (figure as SolidFigure).FigureBrush = (Brush)(this as SolidFigure).FigureBrush.Clone();
            (figure as Figure).Name = this.Name;
            (figure as SolidFigure).textRect = new RectangleF(this.textRect.Location, this.textRect.Size);
            return figure;
        }
    }
    //прямоугольник
    [Serializable]
    public partial class RectFigure : SolidFigure
    {
        public RectFigure()
        {
            SetRandomName8();
            Path.AddRectangle(new RectangleF(-defaultSize, -defaultSize/2, 2*defaultSize, defaultSize));
            textRect = new RectangleF(-defaultSize + 3, -defaultSize / 2 + 2, 2 * defaultSize - 6, defaultSize - 4);
        }
    }
    //надпись
    [Serializable]
    public partial class LabelFigure : SolidFigure
    {
        //возможность двигать метку независимо от главной фигуры
        protected bool freeMoveEnable = true;
        public bool FreeMoveEnable
        {
            get { return freeMoveEnable; }
            set { freeMoveEnable = value; }
        }
        public LabelFigure()
        {
            SetRandomName8();
            Path.AddRectangle(new RectangleF(-defaultSize, -defaultSize / 2, 2 * defaultSize, defaultSize));
            textRect = new RectangleF(-defaultSize - 3, -defaultSize / 2 - 3, 2 * defaultSize + 6, defaultSize + 15);
            //опции изменения размера
            resizeDirections = ResizeDirections.None;
            autoSizeEnable = true;
            //настройка внешнего вида
            FigurePen = Pens.Transparent;
            FigureBrush = Brushes.Transparent;
        }
    }
    //скругленный прямоугольник
    [Serializable]
    public partial class RoundRectFigure : SolidFigure
    {
        public RoundRectFigure()
        {
            SetRandomName8();
            float diameter = 16f; 
            SizeF sizeF = new SizeF( diameter, diameter );
            RectangleF arc = new RectangleF( -defaultSize, -defaultSize/2, sizeF.Width, sizeF.Height );
            Path.AddArc( arc, 180, 90 );
            arc.X = defaultSize-diameter;
            Path.AddArc( arc, 270, 90 );
            arc.Y = defaultSize/2-diameter;
            Path.AddArc( arc, 0, 90 );
            arc.X = -defaultSize;
            Path.AddArc( arc, 90, 90 );
            Path.CloseFigure(); 

            textRect = new RectangleF(-defaultSize + 3, -defaultSize / 2 + 2, 2 * defaultSize - 6, defaultSize - 4);
        }
    }

    //ромб
    [Serializable]
    public partial class RhombFigure : SolidFigure
    {
        public RhombFigure()
        {
            SetRandomName8();
            Path.AddPolygon(new PointF[]{
                new PointF(-defaultSize, 0),
                new PointF(0, -defaultSize/2),
                new PointF(defaultSize, 0),
                new PointF(0, defaultSize/2)
            });
            textRect = new RectangleF(-defaultSize/2, -defaultSize / 4, defaultSize, defaultSize/2);
        }
    }

    //паралелограмм
    [Serializable]
    public partial class ParalelogrammFigure : SolidFigure
    {
        public ParalelogrammFigure()
        {
            SetRandomName8();
            float shift = 8f;
            Path.AddPolygon(new PointF[]{
                new PointF(-defaultSize + shift/2, -defaultSize/2),
                new PointF(defaultSize + shift/2, -defaultSize/2),
                new PointF(defaultSize - shift/2, defaultSize/2),
                new PointF(-defaultSize - shift/2, defaultSize/2),
            });
            textRect = new RectangleF(-defaultSize + shift / 2, -defaultSize / 2 + 2, 2 * defaultSize - shift, defaultSize - 4);
        }
    }

    //эллипс
    [Serializable]
    public partial class EllipseFigure : SolidFigure
    {
        public EllipseFigure()
        {
            SetRandomName8();
            Path.AddEllipse(new RectangleF(-defaultSize, -defaultSize/2, defaultSize*2, defaultSize));
            textRect = new RectangleF(-defaultSize / 1.4f, -defaultSize / 2 / 1.4f , 2 * defaultSize / 1.4f, defaultSize / 1.4f);
        }
    }
    public delegate bool CheckConnectionDelegate();
    public interface IConnectable
    {
        void Connect();
        void Disconnect();
        bool IsConnected { get; set; }
        bool EnableMultipleConnection{ get; set; }
        ConnectorOrientations Orientation{ get; set; }
        PointF Location { get; set; }
        float X { set; get; }
        float Y { set; get; }
        Figure MainFigure { get; set;}
        Figure OwnerFigure { get; set; }
        ConnectorTypes Type { get; set; }
        ConnectorDirections Direction { get; set; }
        CheckConnectionDelegate CheckConnection { get;  set; }
        bool IsConnectionOK();
    }
    

    //соединительная линия
    [Serializable]
    public partial class LineFigure : Figure
    {
        public IConnectable From;
        public IConnectable To;
        static Pen clickPen = new Pen(Color.Transparent, 6);
        public LineFigure()
        {
            SetRandomName8();
        }
        public LineFigure(IConnectable from, IConnectable to)
        {
            SetRandomName8();
            From = from; To = to;
        }
        public override void Draw(Graphics gr)
        {
            if (From == null && To == null)
            {
                if (Path.PointCount >= 2)
                    gr.DrawPath(FigurePen, Path);
                return;
            }
            else
                RecalcPath();
            gr.DrawPath(FigurePen, Path);
        }
        //смещение линии
        public override void Offset(float dx, float dy)
        {
            if (Path.PointCount > 0)
            {
                for (int i = 0; i < Path.PathPoints.Length; i++)
                {
                    Path.PathPoints[i].X += dx;
                    Path.PathPoints[i].Y += dy;
                }
                RecalcPath();
                OffsetEndMarkers(dx, dy);
            }
        }
        //смещение концевых маркеров
        protected virtual void OffsetEndMarkers(float dx, float dy)
        {
            //если какой-то из концов линии не подключен к фигуре,
            //то смещаем маркеры тоже
            if (From != null && From is Marker)
            {
                From.X += dx;
                From.Y += dy;
            }
            if (To != null && To is Marker)
            {
                To.X += dx;
                To.Y += dy;
            }
        }
        public override bool IsInsidePoint(Point p)
        {
            if (From == null || To == null)
            {
                if(Path.PointCount < 2)
                    return false;
            }
            else
                RecalcPath();
            return Path.IsOutlineVisible(p, clickPen);
        }
        //проверяет, находится ли линия в заданном прямоугольнике
        public bool IsLineInRect(RectangleF rect)
        {
            if (Path.PointCount == 0)
                return false;
            foreach (PointF p in Path.PathPoints)
            {
                if (!rect.Contains(p))//точка пути не содержится - линия не содержится
                    return false;
            }
            return true;
        }
        protected virtual void RecalcPath()
        {
            PointF[] points = null;
            if(Path.PointCount>0)
                points = Path.PathPoints;
            
            if (From == null || To == null ||
                Path.PointCount!=2 || points[0]!=From.Location || points[1]!=To.Location)
            {
                Path.Reset();
                if ((From == null)&&(To != null)) //проверяем концы линии
                {
                    Path.AddLine(points[0], To.Location);
                }
                else if ((To == null)&&(From != null))
                {
                    Path.AddLine(From.Location, points[1]);
                }
                else if ((To != null) && (From != null))
                    Path.AddLine(From.Location, To.Location);
                else
                    Path.AddLine(points[0], points[1]);
            }
        }

        public override List<Marker> CreateMarkers(Diagram diagram)
        {
            List<Marker> markers = new List<Marker>();
            EndLineMarker m1 = new EndLineMarker(diagram, 0);
            m1.targetFigure = this;
            EndLineMarker m2 = new EndLineMarker(diagram, 1);
            m2.targetFigure = this;

            markers.Add(m1);
            markers.Add(m2);

            return markers;
        }
        public override RectangleF Bounds
        {
            get { return Path.GetBounds(); }
        }
        public override object Clone()
        {
            LineFigure line = new LineFigure();
            line.serializablePath = new SerializableGraphicsPath();
            line.Path = (GraphicsPath)this.Path.Clone();
            line.FigurePen = (Pen)this.FigurePen.Clone();
            line.Name = name;
            //если концы линии подключены к маркерам, то клонируем их. Коннекторы не копируем
            if (!(line.From is EndLineMarker))
                (line as LineFigure).From = From;
            else
                (line as LineFigure).From = (IConnectable)(From as SolidFigure).Clone();
            if (!(line.To is EndLineMarker))
                (line as LineFigure).To = To;
            else
                (line as LineFigure).To = (IConnectable)(To as SolidFigure).Clone();
            return line;
        }
    }
    //структуры для определения элементов линии с переломами
    [Serializable]
    public enum LedgeDirections { Vertical, Horizontal };
    [Serializable]
    public class LedgePoint : ICloneable
    {
        public float coodinate;
        public LedgeDirections dir;
        public object Clone()
        {
            LedgePoint lp = new LedgePoint();
            lp.coodinate = coodinate;
            lp.dir = dir;
            return lp as Object;
        }
    }
    //линия с "переломом"
    [Serializable]
    public partial class LedgeLineFigure : LineFigure
    {
        /// <summary>
        /// Отступ от границ блоков при автопостроении линии
        /// </summary>
        private static int blockOffset = 10;
        //список точек перелома 
        public List<LedgePoint> ledgePositions = new List<LedgePoint>();

        public LedgeLineFigure()
        {
            SetRandomName8();
        }
        protected bool IsBothFiguresOrientationHorizontal()
        {
            if (To.Orientation < ConnectorOrientations.TopToBottom
                && From.Orientation < ConnectorOrientations.TopToBottom)
                return true;
            return false;
        }
        protected bool IsBothFiguresOrientationVertical()
        {
            if (To.Orientation >= ConnectorOrientations.TopToBottom
                && From.Orientation >= ConnectorOrientations.TopToBottom)
                return true;
            return false;
        }
        protected void AddNewLedge(int index, float coord, LedgeDirections dir)
        {
            LedgePoint lp = new LedgePoint();
            lp.coodinate = coord;
            lp.dir = dir;
            if (index == ledgePositions.Count)
                ledgePositions.Add(lp);
            else if(index >= 0)
                ledgePositions.Insert(index, lp);
        }
        protected void AddLedgeForSameOrientation()
        {
            if (IsBothFiguresOrientationHorizontal())
                AddNewLedge(0, (From.Location.X + To.Location.X) / 2,
                    LedgeDirections.Vertical);
            else if (IsBothFiguresOrientationVertical())
                AddNewLedge(0, (From.Location.Y + To.Location.Y) / 2,
                    LedgeDirections.Horizontal);
        }
        protected override void RecalcPath()
        {
            PointF[] points = null;
            int lastLedgeIndex = ledgePositions.Count - 1;           
            if (To != null && From != null)
            {
                //если число точек нулевое и при этом ориентация маркеров одинакова, добавляем маркер
                if(ledgePositions.Count == 0)
                    AddLedgeForSameOrientation();
                //удаляем лишние маркеры, которые лежат на 1й линии
                if (lastLedgeIndex > 1)
                {
                    if ((ledgePositions[lastLedgeIndex].dir == LedgeDirections.Horizontal &&
                        ledgePositions[lastLedgeIndex].coodinate == To.Location.Y)
                        || (ledgePositions[lastLedgeIndex].dir == LedgeDirections.Vertical &&
                        ledgePositions[lastLedgeIndex].coodinate == To.Location.X))
                            ledgePositions.RemoveAt(lastLedgeIndex);
                }
            }
            if (Path.PointCount > 0)
                points = Path.PathPoints;
            if (To == null || From == null ||
                Path.PointCount != (3 + ledgePositions.Count) ||
                points[0] != From.Location ||
                points[points.Length - 1] != To.Location ||
                !LedgePositionsOK(points))
            {
                
                //проверяем, чтобы направления изломов чередовались
                for (int i = 0; i < ledgePositions.Count - 1; i++)
                {
                    if (ledgePositions[i].dir == ledgePositions[i + 1].dir)
                    {
                        //удаляем следующую позицию
                        ledgePositions.RemoveAt(i + 1);
                    }
                }
                //создаем новую линию с изломом
                List<PointF> linePoints = new List<PointF>();
                //подготавливаем позиции точек
                PointF fromLocation, toLocation;
                if (From != null)
                    fromLocation = From.Location;
                else
                    fromLocation = points[0];
                if (To != null)
                    toLocation = To.Location;
                else
                    toLocation = points[points.Length - 1];
                //первая точка - стартовая
                linePoints.Add(fromLocation);
                //вторая точка
                if (ledgePositions.Count > 0)
                {
                    if (ledgePositions[0].dir == LedgeDirections.Vertical)
                        linePoints.Add(new PointF(ledgePositions[0].coodinate, fromLocation.Y));
                    else
                        linePoints.Add(new PointF(fromLocation.X, ledgePositions[0].coodinate));
                    //генерируем все промежуточные точки
                    for (int i = 0; i < ledgePositions.Count - 1; i++)
                    {
                        if (ledgePositions[i].dir == LedgeDirections.Vertical)
                            linePoints.Add(new PointF(ledgePositions[i].coodinate,
                                ledgePositions[i + 1].coodinate));
                        else
                            linePoints.Add(new PointF(ledgePositions[i + 1].coodinate,
                                 ledgePositions[i].coodinate));
                    }
                    //предпоследняя точка
                    if (ledgePositions[ledgePositions.Count - 1].dir == LedgeDirections.Vertical)
                        linePoints.Add(new PointF(ledgePositions[ledgePositions.Count - 1].coodinate, toLocation.Y));
                    else
                        linePoints.Add(new PointF(toLocation.X, ledgePositions[ledgePositions.Count - 1].coodinate));
                }
                else if(From != null)//точек перелома нет. Проверим, куда ставить угол
                {
                    
                    if (From.Orientation <= ConnectorOrientations.RightToLeft)
                    //горизонтальная
                        linePoints.Add(new PointF(toLocation.X, fromLocation.Y));
                    else
                        linePoints.Add(new PointF(fromLocation.X, toLocation.Y));
                }
                //последняя точка
                linePoints.Add(toLocation);
                //соединяем точки линиями
                Path.Reset();
                for (int i = 0; i < linePoints.Count - 1; i++)
                    Path.AddLine(linePoints[i], linePoints[i + 1]);
            }
        }
        private bool RightConnectionOK(IConnectable compareFigure, float x0, float x1)
        {
            float left = compareFigure.MainFigure.Bounds.Left;
            float right = compareFigure.MainFigure.Bounds.Right;
            if (x0 + 4 >= right && x0 > left && x1 + 4 >= right && x1 > left)
                return true;
            return false;
        }
        private bool LeftConnectionOK(IConnectable compareFigure, float x0, float x1)
        {
            float left = compareFigure.MainFigure.Bounds.Left;
            float right = compareFigure.MainFigure.Bounds.Right;
            if (x0 - 4 <= left && x0 < right && x1 - 4 <= left && x1 < right)
                return true;
            return false;
        }
        private bool BottomConnectionOK(IConnectable compareFigure, float y0, float y1)
        {
            float top = compareFigure.MainFigure.Bounds.Top;
            float bottom = compareFigure.MainFigure.Bounds.Bottom;
            if (y0 + 4 >= bottom && y0 > top && y1 + 4 >= bottom && y1 > top)
                return true;
            return false;
        }
        private bool TopConnectionOK(IConnectable compareFigure, float y0, float y1)
        {
            float top = compareFigure.MainFigure.Bounds.Top;
            float bottom = compareFigure.MainFigure.Bounds.Bottom;
            if (y0 - 4 <= top && y0 < bottom && y1 - 4 <= top && y1 < bottom)
                return true;
            return false;
        }
        private bool CheckLineEndHorizontal(IConnectable compareFigure, int insert_index, int delta_index, int prev_point_id, int next_point_id, float delta_Y, bool AddLedges)
        {
            bool result = true;
            PointF[] points = Path.PathData.Points;
            float new_Y_coord;
            if(delta_Y > 0)
                new_Y_coord = compareFigure.MainFigure.Bounds.Bottom + delta_Y;
            else
                new_Y_coord = compareFigure.MainFigure.Bounds.Top + delta_Y;
            //проверяем правый порт
            if (compareFigure.Orientation <= ConnectorOrientations.RightToLeft &&
                compareFigure.Location.X + 4 >= compareFigure.MainFigure.Bounds.Right)
            {
                if (!RightConnectionOK(compareFigure, points[prev_point_id].X, points[next_point_id].X))
                {
                    if (AddLedges)
                    {
                        AddNewLedge(insert_index, new_Y_coord, LedgeDirections.Horizontal);
                        AddNewLedge(insert_index + delta_index, compareFigure.MainFigure.Bounds.Right + blockOffset, LedgeDirections.Vertical);
                    }
                    result = false;
                }
            }
            //проверяем левый порт
            if (compareFigure.Orientation <= ConnectorOrientations.RightToLeft &&
                compareFigure.Location.X - 4 <= compareFigure.MainFigure.Bounds.Left)
            {
                if (!LeftConnectionOK(compareFigure, points[prev_point_id].X, points[next_point_id].X))
                {
                    if (AddLedges)
                    {
                        AddNewLedge(insert_index, new_Y_coord, LedgeDirections.Horizontal);
                        AddNewLedge(insert_index + delta_index, compareFigure.MainFigure.Bounds.Left - blockOffset, LedgeDirections.Vertical);
                    }
                    result = false;
                }
            }
            return result;
        }
        private bool CheckLineEndVertical(IConnectable compareFigure, int insert_index, int delta_index, int prev_point_id, int next_point_id, float delta_X, bool AddLedges)
        {
            bool result = true;
            PointF[] points = Path.PathData.Points;
            float new_X_coord;
            if (delta_X > 0)
                new_X_coord = compareFigure.MainFigure.Bounds.Right + delta_X;
            else
                new_X_coord = compareFigure.MainFigure.Bounds.Left + delta_X;
            //проверяем нижний порт
            if (compareFigure.Orientation > ConnectorOrientations.RightToLeft &&
                compareFigure.Location.Y + 4 >= compareFigure.MainFigure.Bounds.Bottom)
            {
                if (!BottomConnectionOK(compareFigure, points[prev_point_id].Y, points[next_point_id].Y))
                {
                    if (AddLedges)
                    {
                        AddNewLedge(insert_index, new_X_coord, LedgeDirections.Vertical);
                        AddNewLedge(insert_index + delta_index, compareFigure.MainFigure.Bounds.Bottom + blockOffset, LedgeDirections.Horizontal);
                    }
                    result = false;
                }
            }
            //проверяем верхний порт
            if (compareFigure.Orientation > ConnectorOrientations.RightToLeft &&
                compareFigure.Location.Y - 4 <= compareFigure.MainFigure.Bounds.Top)
            {
                if (!TopConnectionOK(compareFigure, points[prev_point_id].Y, points[next_point_id].Y))
                {
                    if (AddLedges)
                    {
                        AddNewLedge(insert_index, new_X_coord, LedgeDirections.Vertical);
                        AddNewLedge(insert_index + delta_index, compareFigure.MainFigure.Bounds.Top - blockOffset, LedgeDirections.Horizontal);
                    }
                    result = false;
                }
            }
            return result;
        }
        //проверяет, не пересекает ли линия фигуры
        //а также достраивает изломы, если это нарушается и AddLedges == true
        protected bool CheckIntersections(bool AddLedges)
        {
            int last = Path.PathData.Points.Length - 1;
            if (last < 1)
                return false;
            bool result = true;
            //проверяем начало линии
            float delta_Y = (From.Y < To.Y ? blockOffset : -blockOffset);
            float delta_X = (From.X < To.X ? blockOffset : -blockOffset);
            if (From.MainFigure != null)
            {
                result = CheckLineEndHorizontal(From, 0, 0, 0, 1, delta_Y, AddLedges);
                if (!result && !AddLedges)
                    return false;
                result = CheckLineEndVertical(From, 0, 0, 0, 1, delta_X, AddLedges);
                if(AddLedges)
                    RecalcPath();
                else if(!result)
                    return false;
            }
            delta_X = -delta_X;
            delta_Y = -delta_Y;
            //проверяем конец линии
            if (To.MainFigure != null)
            {
                result = CheckLineEndHorizontal(To, ledgePositions.Count, 1, last - 1, last, delta_Y, AddLedges);
                if (!result && !AddLedges)
                    return false;
                result = CheckLineEndVertical(To, ledgePositions.Count, 1, last - 1, last, delta_X, AddLedges);
            }
            return result;
        }
        /// <summary>
        /// Перестраивает линию оптимальным образом.
        /// Вызывать при новом подключении линии к фигурам / при движении фигур
        /// </summary>
        public void RebuildLineOptimal()
        {
            //Если какой-либо конец не подключен, фукнция не имеет смысла
            if (From == null || To == null)
                return;
            //Перестраиваем линию, только если наблюдаются нарушения
            //***(закомментировано - это нетипичное, но возможное поведение)***
            /*if (!CheckIntersections(false))
            {*/
                ledgePositions.Clear();
                //добавляем излом, если у выходных портов одинаковая ориентация
                AddLedgeForSameOrientation();
                //проверка на тот случай, если первая/последняя линия пересекает фигуру
                RecalcPath();
                
                int Q = ledgePositions.Count;
                CheckIntersections(true);
                //если добавили с обоих концов по 2 маркера, удалим средний
                //но это в том случае, если маркер 2 не находится на уровне
                //второй фигуры
                bool isOK = false;
                if (IsBothFiguresOrientationHorizontal() || IsBothFiguresOrientationVertical())
                    isOK = true;
                if (ledgePositions.Count > 1 && To.MainFigure != null)
                {
                    if (ledgePositions[1].dir == LedgeDirections.Horizontal)
                    {
                        if (ledgePositions[1].coodinate >= To.MainFigure.Bounds.Top &&
                            ledgePositions[1].coodinate <= To.MainFigure.Bounds.Bottom)
                            isOK = false;
                    }
                    else
                    {
                        if (ledgePositions[1].coodinate >= To.MainFigure.Bounds.Left &&
                           ledgePositions[1].coodinate <= To.MainFigure.Bounds.Right)
                            isOK = false;
                    }
                }
                //собственно, удаление маркера
                if (ledgePositions.Count - Q == 4 && isOK)
                    ledgePositions.RemoveAt(Q + 1);
                RecalcPath();
                //провека, если линия выгнута дугой: в этом случае ее нужно отодвинуть от коннекторов
                BuildArc();
           // }
        }
        protected void BuildArc()
        {
            int delta = 2*blockOffset;
            if (Path.PointCount == 4)
            {
                if (ledgePositions[0].dir == LedgeDirections.Horizontal
                    && Math.Abs(Path.PathPoints[0].Y - Path.PathPoints[1].Y) < delta
                    && Math.Abs(Path.PathPoints[2].Y - Path.PathPoints[3].Y) < delta)
                {
                    if (To.Orientation == ConnectorOrientations.TopToBottom && To.Direction == ConnectorDirections.Input ||
                        To.Orientation == ConnectorOrientations.BottomToTop && To.Direction == ConnectorDirections.Output)
                        delta *= -1;
                    ledgePositions[0].coodinate += delta;
                }
            }
        }
        //определяет, совпадают ли позиции точек перелома с 
        //координатами линии, заданными в GraphicPath'e
        protected bool LedgePositionsOK(PointF[] points)
        {
            LedgePoint p;
            for(int i = 0; i < ledgePositions.Count; i++)
            {
                p = ledgePositions[i];
                if (p.dir == LedgeDirections.Vertical)
                {
                    if (p.coodinate != points[i + 1].X)
                        return false;
                }
                else
                {
                    if (p.coodinate != points[i + 1].Y)
                        return false;
                }
            }
            return true;
        }
        public override List<Marker> CreateMarkers(Diagram diagram)
        {
            if(From != null && To != null)
                RecalcPath();
            List<Marker> markers = new List<Marker>();
            //маркеры концов линии
            EndLineMarker m1 = new EndLineMarker(diagram, 0);
            m1.targetFigure = this;
            EndLineMarker m2 = new EndLineMarker(diagram, 1);
            m2.targetFigure = this;
            markers.Add(m1);
            markers.Add(m2);
            LedgeMarker m3;
            //маркеры переломов
            for (int i = 0; i < ledgePositions.Count; i++)
            {
                m3 = new LedgeMarker(i);
                m3.targetFigure = this;
                m3.UpdateLocation();
                markers.Add(m3);
            }
            return markers;
        }
        bool isIConnectableVertical(IConnectable fig)
        {
            if (fig.Orientation == ConnectorOrientations.BottomToTop ||
                fig.Orientation == ConnectorOrientations.TopToBottom)
                return true;
            return false;
        }
        public override void Offset(float dx, float dy)
        {
            if (Path.PointCount > 0)
            {
                for (int i = 0; i < Path.PathPoints.Length; i++)
                {
                    Path.PathPoints[i].X += dx;
                    Path.PathPoints[i].Y += dy;
                }
                //сдвиг всех точек перелома
                LedgePoint p;
                for (int i = 0; i < ledgePositions.Count; i++)
                {
                    p = ledgePositions[i];
                    if (p.dir == LedgeDirections.Vertical)
                        p.coodinate += dx;
                    else
                        p.coodinate += dy;
                }
                OffsetEndMarkers(dx, dy);
                RecalcPath();
            }
        }
        public override object Clone()
        {
            LedgeLineFigure line = new LedgeLineFigure();
            line.Name = Name;
            line.serializablePath = new SerializableGraphicsPath();
            line.FigurePen = (Pen)(this as Figure).FigurePen.Clone();
            line.Path = (GraphicsPath)(this as Figure).Path.Clone();
            for (int i = 0; i < ledgePositions.Count; i++)
                line.ledgePositions.Add(ledgePositions[i].Clone() as LedgePoint);
            line.To = To;
            line.From = From;
            return line as Object;
        }
    }
    //сериализуемая обертка над GraphicsPath и инструментами отрисовки
    [Serializable]
    public class SerializableGraphicsPath: ISerializable
    {
        public GraphicsPath path = new GraphicsPath();
        public Pen pen = new Pen(Brushes.Black);
        public Brush brush = new SolidBrush(Color.White);

        public SerializableGraphicsPath()
        {
            
        }

        private SerializableGraphicsPath(SerializationInfo info, StreamingContext context)
        {
            //Path
            if (!info.GetBoolean("path_empty"))
            {
                PointF[] points = (PointF[])info.GetValue("p", typeof(PointF[]));
                byte[] types = (byte[])info.GetValue("t", typeof(byte[]));
                path = new GraphicsPath(points, types);
            }
            else
                path = new GraphicsPath();
            //Pen
            pen.Color = (Color)info.GetValue("p_color", typeof(Color));
            pen.Width = (float)info.GetValue("p_width", typeof(float));
            //Brush
            if (info.GetBoolean("b_is_gradient"))
            {
                brush = new LinearGradientBrush(
                    (RectangleF)info.GetValue("b_rect", typeof(RectangleF)),
                    (Color)info.GetValue("b_color1", typeof(Color)),
                    (Color)info.GetValue("b_color2", typeof(Color)),(float)0);
            }
            else
                brush = new SolidBrush((Color)info.GetValue("b_color", typeof(Color)));
        }

        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            if (path.PointCount > 0)
            {
                //path
                info.AddValue("path_empty", false);
                info.AddValue("p", path.PathPoints);
                info.AddValue("t", path.PathTypes);
            }
            else
                info.AddValue("path_empty", true);
            //pen
            info.AddValue("p_color", pen.Color);
            info.AddValue("p_width", pen.Width);
            //brush
            info.AddValue("b_is_gradient", (brush is LinearGradientBrush));
            if (brush is LinearGradientBrush)
            {
                info.AddValue("b_color1", (brush as LinearGradientBrush).LinearColors[0]);
                info.AddValue("b_color2", (brush as LinearGradientBrush).LinearColors[1]);
                info.AddValue("b_rect", (brush as LinearGradientBrush).Rectangle);
            }
            else
            {
                info.AddValue("b_color", (brush as SolidBrush).Color);
                }
        }
    }

    [Serializable]
    public abstract class Marker : SolidFigure
    {
        protected static new int defaultSize = 2;
        public Figure targetFigure;

        public override bool IsInsidePoint(Point p)
        {
            if (p.X < location.X - defaultSize || p.X > location.X + defaultSize)
                return false;
            if (p.Y < location.Y - defaultSize || p.Y > location.Y + defaultSize)
                return false;

            return true;
        }

        public override void Draw(Graphics gr)
        {
            gr.DrawRectangle(Pens.Black, location.X - defaultSize, location.Y - defaultSize, defaultSize * 2, defaultSize * 2);
            gr.FillRectangle(Brushes.BlueViolet, location.X - defaultSize, location.Y - defaultSize, defaultSize * 2, defaultSize * 2);
        }
        //функция обновляет положение метки, если изменяемая фигура является
        //частью сложной фигуры с надписью
        public void UpdateLabelLocation(float dx, float dy)
        {
            if (targetFigure is SolidFigure)
            {
                if (!((targetFigure as SolidFigure).ownerFigure is ComplexFigure))
                    return;
                ComplexFigure CF = ((targetFigure as SolidFigure).ownerFigure as ComplexFigure);
                PointF labelP;
                foreach (SolidFigure fig in CF.SolidFigures)
                {
                    if(fig is LabelFigure)
                    {
                        LabelFigure label = (fig as LabelFigure);
                        //пересчитываем положение рамки, если маркер на нее наехал
                        labelP = label.location;
                        RectangleF testBounds = (targetFigure as SolidFigure).Bounds;
                        testBounds.Width += Math.Abs(4 * dx);
                        testBounds.Height += Math.Abs(4 * dy);
                        testBounds.X -= Math.Abs(2*dx);
                        testBounds.Y -= Math.Abs(2*dy);
                        if (label.Bounds.IntersectsWith(testBounds) && label.IsInsidePoint(new Point((int)labelP.X, (int)(location.Y + 2 * defaultSize * dy / Math.Abs(dy)))))//наехал по оси Y
                            label.location.Y += dy;
                        if (label.Bounds.IntersectsWith(testBounds) && label.IsInsidePoint(new Point((int)(location.X + 2 * defaultSize * dx / Math.Abs(dx)), (int)labelP.Y)))//наехал по оси X
                            label.location.X += dx;
                    }
                }
            }
        }
        public abstract void UpdateLocation();
    }

    public partial class BottomRightMarker : Marker
    {
        public override void UpdateLocation()
        {
            RectangleF bounds = (targetFigure as SolidFigure).Bounds; location = new Point((int)Math.Round(bounds.Right) - defaultSize / 2, (int)Math.Round(bounds.Bottom) - defaultSize / 2);  }
        public override void Offset(float dx, float dy)
        {
            base.Offset(dx, dy); UpdateLabelLocation(dx, dy);
        (targetFigure as SolidFigure).location = PointF.Add(
            (targetFigure as SolidFigure).location, new SizeF(dx / 2.0F, dy / 2.0F));
            (targetFigure as SolidFigure).Size =
                SizeF.Add((targetFigure as SolidFigure).Size, new SizeF(dx, dy));}
    }
    public partial class BottomLeftMarker : Marker
    {
        public override void UpdateLocation() { RectangleF bounds = (targetFigure as SolidFigure).Bounds; location = new Point((int)Math.Round(bounds.Left) + defaultSize / 2, (int)Math.Round(bounds.Bottom) - defaultSize / 2); }
        public override void Offset(float dx, float dy)
        {
            base.Offset(dx, dy); UpdateLabelLocation(dx, dy);
        (targetFigure as SolidFigure).location = PointF.Add((targetFigure as SolidFigure).location, new SizeF(dx / 2.0F, dy / 2.0F));
            (targetFigure as SolidFigure).Size = SizeF.Add((targetFigure as SolidFigure).Size, new SizeF(-dx, dy)); }
    }
    public partial class TopRightMarker : Marker
    {
        public override void UpdateLocation() { RectangleF bounds = (targetFigure as SolidFigure).Bounds; location = new Point((int)Math.Round(bounds.Right) - defaultSize / 2, (int)Math.Round(bounds.Top) + defaultSize / 2); }
        public override void Offset(float dx, float dy)
        {
            base.Offset(dx, dy); UpdateLabelLocation(dx, dy);
        (targetFigure as SolidFigure).location = PointF.Add((targetFigure as SolidFigure).location, new SizeF(dx / 2.0F, dy / 2.0F));
            (targetFigure as SolidFigure).Size = SizeF.Add((targetFigure as SolidFigure).Size, new SizeF(dx, -dy));}        
    }
    public partial class TopLeftMarker : Marker
    {
        public override void UpdateLocation() { RectangleF bounds = (targetFigure as SolidFigure).Bounds; location = new Point((int)Math.Round(bounds.Left) + defaultSize / 2, (int)Math.Round(bounds.Top) + defaultSize / 2); }
        public override void Offset(float dx, float dy)
        {
            base.Offset(dx, dy); UpdateLabelLocation(dx, dy);
        (targetFigure as SolidFigure).location = PointF.Add((targetFigure as SolidFigure).location, new SizeF(dx / 2.0F, dy / 2.0F));
            (targetFigure as SolidFigure).Size = SizeF.Add((targetFigure as SolidFigure).Size, new SizeF(-dx, -dy));}
    }
    public abstract class WidthMarker : Marker
    {
        
    }
    public abstract class HeightMarker : Marker
    {

    }
    public partial class RightWidthMarker : WidthMarker
    {
        public override void Offset(float dx, float dy)
        {
            base.Offset(dx, 0);
            (targetFigure as SolidFigure).location = PointF.Add(
                (targetFigure as SolidFigure).location, new SizeF(dx/2.0F, 0));
            (targetFigure as SolidFigure).Size =
                SizeF.Add((targetFigure as SolidFigure).Size, new SizeF(dx, 0));
            UpdateLabelLocation(dx, 0);
        }
        public override void UpdateLocation()
        {
            RectangleF bounds = (targetFigure as SolidFigure).Bounds;
            location = new PointF((float)Math.Round(bounds.Right) - defaultSize / 2, (float)(targetFigure as SolidFigure).location.Y);
        }
    }
    public partial class LeftWidthMarker : WidthMarker
    {
        public override void Offset(float dx, float dy)
        {
            base.Offset(dx, 0);
            (targetFigure as SolidFigure).location = PointF.Add(
                (targetFigure as SolidFigure).location, new SizeF(dx / 2.0F, 0));
            (targetFigure as SolidFigure).Size =
                SizeF.Add((targetFigure as SolidFigure).Size, new SizeF(-dx, 0));
            UpdateLabelLocation(dx, 0);
        }
        public override void UpdateLocation()
        {
            RectangleF bounds = (targetFigure as SolidFigure).Bounds;
            location = new PointF((float)Math.Round(bounds.Left) + defaultSize / 2, (float)(targetFigure as SolidFigure).location.Y);
        }
    }
    public partial class TopHeightMarker : HeightMarker
    {
        public override void Offset(float dx, float dy)
        {
            base.Offset(0, dy);
            (targetFigure as SolidFigure).location = PointF.Add(
                (targetFigure as SolidFigure).location, new SizeF(0, dy / 2.0F));
            (targetFigure as SolidFigure).Size =
                SizeF.Add((targetFigure as SolidFigure).Size, new SizeF(0, -dy));
            UpdateLabelLocation(0, dy);
        }
        public override void UpdateLocation()
        {
            RectangleF bounds = (targetFigure as SolidFigure).Bounds;
            location = new PointF(
                (float)(targetFigure as SolidFigure).location.X,
                (float)Math.Round(bounds.Top) + defaultSize / 2);
        }
    }
    public partial class BottomHeightMarker : HeightMarker
    {
        public override void Offset(float dx, float dy)
        {
            base.Offset(0, dy);
            (targetFigure as SolidFigure).location = PointF.Add(
                (targetFigure as SolidFigure).location, new SizeF(0, dy / 2.0F));
            (targetFigure as SolidFigure).Size =
                SizeF.Add((targetFigure as SolidFigure).Size, new SizeF(0, dy));
            UpdateLabelLocation(0, dy);
        }
        public override void UpdateLocation()
        {
            RectangleF bounds = (targetFigure as SolidFigure).Bounds;
            location = new PointF(
                (float)(targetFigure as SolidFigure).location.X,
                (float)Math.Round(bounds.Bottom) - defaultSize / 2);
        }
    }
    public delegate void ConnectBlocksDelegate(string NameFrom, string NameTo, int param);
    public delegate void DisconnectBlocksDelegate(string NameFrom, string NameTo);
    [Serializable]
    public partial class EndLineMarker : Marker, IConnectable
    {
        int pointIndex;
        ConnectorTypes type;
        Diagram diagram;
        public static ConnectBlocksDelegate ConnectBlocks;
        public static DisconnectBlocksDelegate DisconnectBlocks;

        public EndLineMarker(Diagram diagram, int pointIndex)
        {
            this.diagram = diagram;
            this.pointIndex = pointIndex;
        }

        public override void UpdateLocation()
        {
            LineFigure line = (targetFigure as LineFigure);
            //if (line.From == null || line.To == null)
            //    return;
           // {
                //если у линии оторванный конец, то сажаем на него маркер
                if (line.From == null && pointIndex == 0)
                {
                    line.From = this;
                    location = new PointF(line.Path.PathPoints[0].X, line.Path.PathPoints[0].Y);
                    return;
                }
                else if (line.To == null && pointIndex == 1)
                {
                    line.To = this;
                    location = new PointF(line.Path.PathPoints[line.Path.PointCount - 1].X,
                        line.Path.PathPoints[line.Path.PointCount - 1].Y);
                    return;
                }
                
            //}
            //фигура, с которой связана линия
            IConnectable figure = pointIndex == 0 ? line.From : line.To;
            location = figure.Location;
        }
        public override void Draw(Graphics gr)
        {
            gr.DrawRectangle(Pens.Black, location.X - defaultSize, location.Y - defaultSize, defaultSize * 2, defaultSize * 2);
            gr.FillRectangle(Brushes.Red, location.X - defaultSize, location.Y - defaultSize, defaultSize * 2, defaultSize * 2);
        }
        public override void Offset(float dx, float dy)
        {
            base.Offset(dx, dy);
            //линия маркера
            LineFigure line = (targetFigure as LineFigure);
            //ищем фигуру под маркером
            SolidFigure figure = null;
            for (int i = diagram.figures.Count - 1; i >= 0; i--)
            {
                if (diagram.figures[i].IsInsidePoint(new Point((int)location.X, (int)location.Y)))
                {
                    if(diagram.figures[i] is IConnectable)
                    {
                        figure = (SolidFigure)diagram.figures[i];
                        break;
                    }
                    else if (diagram.figures[i] is ComplexFigure)
                    {
                        //ищем порт комплексной фигуры
                        foreach (SolidFigure SF in (diagram.figures[i] as ComplexFigure).SolidFigures)
                        {
                            if (SF.IsInsidePoint(new Point((int)location.X, (int)location.Y))
                                && (SF is IConnectable))
                            {
                                if ((SF as IConnectable).EnableMultipleConnection
                                    || !(SF as IConnectable).IsConnected
                                    || ((SF as IConnectable).IsConnected
                                    && ((SF == line.To) || (SF == line.From))
                                    ))
                                    figure = SF;
                                else
                                    throw new ConnectorException("PortMultipleConnectionException");
                                break;
                            }
                        }
                        break;
                    }
                }
            }
            if (figure == null)
                figure = this;//если под маркером нет фигуры, то просто коннектим линию к самому маркеру
            
            
            //не позволяем конектится самому к себе
            if (line.From == figure || line.To == figure)
                return;
            //включаем фигуру
            if (figure is IConnectable)
                (figure as IConnectable).Connect();
            //Осуществление встроенной проверки на только что подлюченную фигуру
            if (!(figure as IConnectable).IsConnectionOK())
            {
                throw new ConnectorException("Нарушение логики подлючения коннектора!");
            }
            string old_from_name = "";
            string old_to_name = "";
            //отключаем от старый коннектор
            //обновляем коннекторы линии
            if (pointIndex == 0)
            {
                if (line.From != figure && line.From != null && !(line.From is Marker))
                {
                    (line.From as IConnectable).Disconnect();
                    old_from_name = line.From.OwnerFigure.Name;
                }
                line.From = (figure as IConnectable);
            }
            else
            {
                if (line.To != figure && line.To != null && !(line.To is Marker))
                {
                    (line.To as IConnectable).Disconnect();
                    old_to_name = line.To.OwnerFigure.Name;
                }
                line.To = (figure as IConnectable);
            }           
            //Изменение стиля линий
            if (line.From.OwnerFigure != null)
            {
                line.FigurePen = (line.From.OwnerFigure as ISolidFigure).FigurePen;
                //если линия сущностей, то делаем жирной
                if (line.From.Type == ConnectorTypes.Entity)
                    line.FigurePen = new Pen(line.FigurePen.Color, 2);
                else
                    line.FigurePen = new Pen(line.FigurePen.Color, 1);
                //окрасим порт назначения
                if (line.To != null)
                {
                    (line.To as SolidFigure).FigurePen = line.FigurePen;
                    (line.To as SolidFigure).FigureBrush = new SolidBrush(line.FigurePen.Color);
                }
            }
            //Проверка на правильность соединения портов, которая работает,
            //когда линия подключена к фигурам с обоих концов (а не 2м маркерам / фигуре и маркеру)
            if ((line.From is IConnectable && line.To is IConnectable)
                && !(line.From is EndLineMarker)
                && !(line.To is EndLineMarker))
            {
                //если ошибка - отцепляем линию и приклеиваем к маркеру
                if (line.From.MainFigure == line.To.MainFigure)
                {
                    if (pointIndex == 0) line.From = this; else line.To = this;
                    throw new ConnectorException("SameBlockConnectorException");
                }
                if (line.From.Type != line.To.Type)
                {
                    if (pointIndex == 0) line.From = this; else line.To = this;
                    throw new ConnectorException("PortTypeException");
                }
                if (line.From.Direction == line.To.Direction)
                {
                    if (pointIndex == 0) line.From = this; else line.To = this;
                    throw new ConnectorException("PortDirectionException");
                } 
                //если проверка прошла и направление линии некорректное,
                //то разворачиваем линию по направлению портов Output - Input
                if (line.From.Direction == ConnectorDirections.Input)
                {
                    IConnectable tmp;
                    tmp = line.From;
                    line.From = line.To;
                    line.To = tmp;
                }
            }
            //все проверки прошли, линия корректна. Выполняем отсоединение/соединение блоков логики
            if (old_from_name != "" && line.To != null)
                if(line.To.OwnerFigure != null)
                    EndLineMarker.DisconnectBlocks(old_from_name, line.To.OwnerFigure.Name);
            if (old_to_name != "" && line.From != null)
                if (line.From.OwnerFigure != null)
                    EndLineMarker.DisconnectBlocks(line.From.OwnerFigure.Name, old_to_name);
            int param = 0;
            if ((old_to_name == "" || old_from_name == "") && line.From != null && line.To != null)
                if (line.From.OwnerFigure != null && line.To.OwnerFigure != null)
                {
                    if (line.From.OwnerFigure is DecisionBlock)
                        param = (line.From.OwnerFigure as DecisionBlock).FindPortDirection(line.From as PortFigure);
                    EndLineMarker.ConnectBlocks(line.From.OwnerFigure.Name, line.To.OwnerFigure.Name, param);
                }
        }
        public void Connect() { }
        public void Disconnect() { }
        public bool EnableMultipleConnection
        {
            get { return false; }
            set { }
        }
        public bool IsConnected
        {
            get { return true; }
            set { }
        }
        public float X
        {
            get { return location.X; }
            set { location.X = value; }
        }
        public float Y
        {
            get { return location.Y; }
            set { location.Y = value; }
        }
        public ConnectorOrientations Orientation
        {
            get { return ConnectorOrientations.LeftToRight; }
            set { }
        }
        public ConnectorDirections Direction
        {
            get { return (pointIndex == 0 ? ConnectorDirections.Output : ConnectorDirections.Input); }
            set { }
        }
        public Figure MainFigure
        {
            get { return null; }
            set { }
        }
        public ConnectorTypes Type
        {
            get { return (ConnectorTypes)Enum.GetValues(type.GetType()).GetValue(0); }
            set { }
        }
        public Figure OwnerFigure
        {
            get { return null; }
            set { }
        }
        public bool IsConnectionOK()
        {
            return true;
        }
        public CheckConnectionDelegate CheckConnection
        {
            set { }
            get { return null; }
        }
    }

    [Serializable]
    public partial class LedgeMarker : Marker
    {
        public int ledgePointIndex;//индекс точки, с которой маркер связан
        
        public LedgeMarker(int ledge_point_id)
        {
            ledgePointIndex = ledge_point_id;
        }
        public override void UpdateLocation()
        {
            float x, y;//новые координаты
            LedgeLineFigure line = (targetFigure as LedgeLineFigure);
            if (line.From == null || line.To == null)
                return;//не обновляем маркеры оторванных концов
            if (ledgePointIndex >= line.ledgePositions.Count)
                return;
            //фигура, с которой связана линия
            if (line.ledgePositions[ledgePointIndex].dir == LedgeDirections.Vertical)
            {
                x = line.ledgePositions[ledgePointIndex].coodinate;
                //первая "половина" координаты Y - предыдущая точка на линии
                if (ledgePointIndex < 1)
                    y = line.From.Location.Y;
                else
                    y = line.ledgePositions[ledgePointIndex - 1].coodinate;
                //вторая "половина" Y - следующая точка на линии
                if (ledgePointIndex == line.ledgePositions.Count - 1)
                    y = (y + line.To.Location.Y) / 2;
                else
                    y = (y + line.ledgePositions[ledgePointIndex + 1].coodinate) / 2;
            }
            else
            {
                y = line.ledgePositions[ledgePointIndex].coodinate;
                //первая "половина" координаты X - предыдущая точка на линии
                if (ledgePointIndex < 1)
                    x = line.From.Location.X;
                else
                    x = line.ledgePositions[ledgePointIndex - 1].coodinate;
                //вторая "половина" X - следующая точка на линии
                if (ledgePointIndex == line.ledgePositions.Count - 1)
                    x = (x + line.To.Location.X) / 2;
                else
                    x = (x + line.ledgePositions[ledgePointIndex + 1].coodinate) / 2;
            }
            location = new Point((int)x, (int)y);
        }

        public override void Offset(float dx, float dy)
        {
            base.Offset(dx, dy);
            //сдвиг соответствующей точки перелома
            if (ledgePointIndex < (targetFigure as LedgeLineFigure).ledgePositions.Count)
            {
                LedgePoint p = (targetFigure as LedgeLineFigure).ledgePositions[ledgePointIndex];
                if (p.dir == LedgeDirections.Vertical)
                    p.coodinate += dx;
                else
                    p.coodinate += dy;
            }
        }
        public override void Draw(Graphics gr)
        {
            gr.DrawRectangle(Pens.Black, location.X - defaultSize, location.Y - defaultSize, defaultSize * 2, defaultSize * 2);
            gr.FillRectangle(Brushes.Red, location.X - defaultSize, location.Y - defaultSize, defaultSize * 2, defaultSize * 2);
        }
    }
}
